/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.inspur.edp.sgf.compile;

import com.inspur.edp.sgf.api.entity.ServiceOperation;
import com.inspur.edp.sgf.api.entity.ServiceParameter;
import com.inspur.edp.sgf.api.entity.SgMetadata;
import org.apache.commons.lang3.StringUtils;

import java.text.MessageFormat;
import java.util.List;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * 功能描述: @ClassName: EapiMethod @Author: Fynn Qi @Date: 2020/9/17 14:10 @Version: V1.0
 */
public class EapiMethod {

    private static final String EAPI_PARAM_TYPE_OBJECT = "object";

    private static final String EAPI_POSITION_BODY = "Body";
    private static final String EAPI_POSITION_HEADER = "Header";
    private static final String EAPI_POSITION_PATH = "Path";
    private static final String EAPI_POSITION_QUERY = "Query";

    private static final String EAPI_SERVICE_INVOKER = "ServiceInvoker";
    private static final String EAPI_SERVICE_UTILS = "EapiServiceUtils";

    private static final Pattern CHARACTER_PATTERN = Pattern.compile("[\u4e00-\u9fa5]");

    public static String getMethods(SgMetadata eapi) {
        StringBuilder sb = new StringBuilder();
        if (eapi == null || eapi.getService() == null || eapi.getService().getOperations() == null || eapi.getService().getOperations().size() <= 0) {
            return sb.toString();
        }
        List<ServiceOperation> operations = eapi.getService().getOperations();
        if (operations == null || operations.size() <= 0) {
            return sb.toString();
        }
        for (ServiceOperation operation : operations) {
            if (EapiRtHandler.isInWhiteList(operation.getCode())) {
                continue;
            }
            sb.append(getMethod(operation, eapi)).append(System.lineSeparator());
        }
        return sb.toString();
    }

    public static String getMethod(ServiceOperation operation, SgMetadata metadata) {
        StringBuilder sb = new StringBuilder();
        sb.append(getMethodAnnotation(operation));
        sb.append(MessageFormat.format("  public {0} {1}({2})'{'", getReturnType(operation), getCamelCode(operation.getCode()), getInputParams(operation))).append(System.lineSeparator());
        sb.append(getMethodBody(operation, metadata));
        sb.append("  }").append(System.lineSeparator());
        return sb.toString();
    }

    public static StringBuilder getMethodBody(ServiceOperation operation, SgMetadata metadata) {
        StringBuilder sb = new StringBuilder();
        //初始化JsonNode集合
        sb.append(getInitJsonNodeArray());
        //解析参数
        sb.append(getJsonNodeParamList(operation));
        //解析扩展属性，放入context上下文中
        sb.append(getContext(metadata));
        //开启分布式事务
        if (operation.isTransaction()) {
            sb.append(useGlobalTransaction(operation));
        }
        //执行调用
        sb.append(getReturnStatement(operation, metadata));
        return sb;
    }

    private static String useGlobalTransaction(ServiceOperation operation) {
        StringBuilder sb = new StringBuilder();
        sb.append("    GlobalTransactionService globalTransactionService=EapiServiceUtils.getGlobalTransactionService();").append(System.lineSeparator());
        sb.append(String.format("    GlobalTransaction transaction = globalTransactionService.begin(\"%s\", Propagation.REQUIRED);", operation.getCode())).append(System.lineSeparator());
        return sb.toString();
    }

    private static String getInitJsonNodeArray() {
        StringBuilder sb = new StringBuilder();
        sb.append("    List<JsonNode> jsonNodes=new ArrayList<JsonNode>();").append(System.lineSeparator());
        return sb.toString();
    }

    private static StringBuilder getJsonNodeParamList(ServiceOperation operation) {
        StringBuilder sb = new StringBuilder();
        List<ServiceParameter> parameters = operation.getParameters();
        if (parameters == null || parameters.size() <= 0) {
            return sb;
        } else {
            List<ServiceParameter> bodyParams = parameters.stream().filter(x -> EAPI_POSITION_BODY.equals(x.getIn())).collect(Collectors.<ServiceParameter>toList());
            int bodyPositionCont = bodyParams.size();
            for (ServiceParameter parameter : parameters) {
                if (parameter.isRetVal()) {
                    continue;
                }
                if (EAPI_POSITION_HEADER.equals(parameter.getIn()) || EAPI_POSITION_PATH.equals(parameter.getIn()) || EAPI_POSITION_QUERY.equals(parameter.getIn())) {
                    //非body位置参数
                    getSimpleParam(sb, parameter);
                } else if (EAPI_POSITION_BODY.equals(parameter.getIn())) {
                    // Body位置只有一个参数，直接添加到JSONNodes列表中
                    if (bodyPositionCont <= 1) {
                        sb.append("    jsonNodes.add(node);");
                        sb.append(System.lineSeparator());
                    } else {
                        sb.append(MessageFormat.format("    jsonNodes.add({0}.getJsonNodeIgnoreCase(\"{1}\",node));", EAPI_SERVICE_UTILS, parameter.getCode()));
                        sb.append(System.lineSeparator());
                    }
                }
            }
        }
        return sb;
    }

    private static void getSimpleParam(StringBuilder sb, ServiceParameter parameter) {
        sb.append(
                MessageFormat.format("    jsonNodes.add(EapiServiceUtils.valueToTree({0}));", parameter.getCode()));
        sb.append(System.lineSeparator());
    }

    private static StringBuilder getContext(SgMetadata metadata) {
        StringBuilder sb = new StringBuilder();
        sb.append("    Map<String, String> context = new HashMap<>(this.getGlobalContext());").append(System.lineSeparator());
        //后续的操作扩展可以在此处添加
        return sb;
    }

    private static StringBuilder getReturnStatement(ServiceOperation operation, SgMetadata metadata) {
        StringBuilder sb = new StringBuilder();
        String resourceType = metadata.getService().getResourceType();
        String resourceId = metadata.getService().getResourceId();
        String operationId = operation.getResourceOpId();
        //是否开启分布式事务
        if (operation.isTransaction()) {
            sb.append("    try {").append(System.lineSeparator());
            if (hasReturnParam(operation)) {
                sb.append(MessageFormat.format("      Object result = {0}.invokeByJsonNode(\"{1}\",\"{2}\",\"{3}\",jsonNodes,context);", EAPI_SERVICE_INVOKER, resourceType, resourceId, operationId)).append(System.lineSeparator());
                sb.append("      globalTransactionService.commit(transaction);").append(System.lineSeparator());
                sb.append("      return result;").append(System.lineSeparator());
            } else {
                sb.append(MessageFormat.format("      {0}.invokeByJsonNode(\"{1}\",\"{2}\",\"{3}\",jsonNodes,context);", EAPI_SERVICE_INVOKER, resourceType, resourceId, operationId)).append(System.lineSeparator());
                sb.append("      globalTransactionService.commit(transaction);").append(System.lineSeparator());
            }
            sb.append("    }").append(System.lineSeparator());
            sb.append("    catch (Exception e){").append(System.lineSeparator());
            sb.append("      globalTransactionService.rollback(transaction);").append(System.lineSeparator());
            sb.append("      throw new RuntimeException(e);").append(System.lineSeparator());
            sb.append("    }").append(System.lineSeparator());
        } else {
            if (hasReturnParam(operation)) {
                sb.append(MessageFormat.format("    return {0}.invokeByJsonNode(\"{1}\",\"{2}\",\"{3}\",jsonNodes,context);", EAPI_SERVICE_INVOKER, resourceType, resourceId, operationId)).append(System.lineSeparator());
            } else {
                sb.append(MessageFormat.format("    {0}.invokeByJsonNode(\"{1}\",\"{2}\",\"{3}\",jsonNodes,context);", EAPI_SERVICE_INVOKER, resourceType, resourceId, operationId)).append(System.lineSeparator());
            }
        }
        return sb;
    }

    private static boolean hasReturnParam(ServiceOperation operation) {
        if (operation.getParameters() == null || operation.getParameters().size() <= 0) {
            return false;
        }
        return operation.getParameters().stream().anyMatch(ServiceParameter::isRetVal);
    }

    private static String getReturnType(ServiceOperation operation) {
        List<ServiceParameter> parameters = operation.getParameters();
        if (Objects.isNull(parameters) || parameters.size() <= 0) {
            return "void";
        } else {
            int count = parameters.stream().filter(ServiceParameter::isRetVal).toArray().length;
            if (count > 0) {
                return "Object";
            } else {
                return "void";
            }
        }
    }

    private static String getInputParams(ServiceOperation operation) {
        StringBuilder sb = new StringBuilder();
        List<ServiceParameter> parameters = operation.getParameters();
        if (Objects.nonNull(parameters) && parameters.size() > 0) {
            for (ServiceParameter parameter : parameters) {
                if (parameter.isRetVal()) {
                    continue;
                }
                switch (parameter.getIn()) {
                    case "Header":
                        getHeaderParamByCondition(sb, parameter);
                        break;
                    case "Query":
                        getQueryParamByCondition(sb, parameter);
                        break;
                    case "Path":
                        getPathParamByCondition(sb, parameter);
                        break;
                    default:
                        // Body位置的参数单独处理
                        break;
                }
            }
            // 处理Body位置的参数
            getBodyParamByCondition(sb, parameters);
        }
        return sb.toString();
    }

    private static void getBodyParamByCondition(StringBuilder sb, List<ServiceParameter> parameters) {
        int count =
                parameters.stream().filter(x -> EAPI_POSITION_BODY.equals(x.getIn())).toArray().length;
        if (count > 0) {
            if (StringUtils.isNotBlank(sb.toString())) {
                // 逗号参数分割
                sb.append(",");
            }
            sb.append(getBodyParam());
        }
    }

    private static String getBodyParam() {
        return "JsonNode node";
    }

    private static void getPathParamByCondition(StringBuilder sb, ServiceParameter parameter) {
        if (StringUtils.isNotBlank(sb.toString())) {
            // 逗号参数分割
            sb.append(",");
        }
        getPathParam(sb, parameter);
    }

    private static void getPathParam(StringBuilder sb, ServiceParameter parameter) {
        sb.append(MessageFormat.format("@PathParam(\"{0}\") {1} {2} ", parameter.getCode(), getParamType(parameter), parameter.getCode()));
    }

    private static void getQueryParamByCondition(StringBuilder sb, ServiceParameter parameter) {
        if (StringUtils.isNotBlank(sb.toString())) {
            // 逗号参数分割
            sb.append(",");
        }
        getQueryParam(sb, parameter);
    }

    private static void getQueryParam(StringBuilder sb, ServiceParameter parameter) {
        sb.append(MessageFormat.format("@QueryParam(\"{0}\") {1} {2} ", parameter.getCode(), getParamType(parameter), parameter.getCode()));
    }

    private static void getHeaderParamByCondition(StringBuilder sb, ServiceParameter parameter) {
        if (StringUtils.isNotBlank(sb.toString())) {
            // 逗号参数分割
            sb.append(",");
        }
        sb.append(getHeaderParam(parameter));
    }

    private static String getHeaderParam(ServiceParameter parameter) {
        StringBuilder sb = new StringBuilder();
        sb.append(MessageFormat.format("@HeaderParam(\"{0}\") {1} {2} ", parameter.getCode(), getParamType(parameter), parameter.getCode()));
        return sb.toString();
    }

    private static String getParamType(ServiceParameter parameter) {
        StringBuilder sb = new StringBuilder();
        // 基本类型
        if (parameter.isBasicType()) {
            if (parameter.isCollection()) {
                sb.append(getListBasicType(parameter));
            } else {
                sb.append(getBasicType(parameter));
            }
        }
        // 复杂类型
        else {
            throw new RuntimeException("不支持复杂类型的Header参数");
        }
        return sb.toString();
    }

    private static StringBuilder getListBasicType(ServiceParameter parameter) {
        StringBuilder sb = new StringBuilder();
        sb.append("String");
        getIteratorList(parameter.getListDeepth(), sb);
        return sb;
    }

    private static StringBuilder getIteratorList(int depth, StringBuilder sb) {
        if (depth > 0) {
            sb.append(MessageFormat.format("List<{0}>", sb.toString()));
            return getIteratorList(depth - 1, sb);
        } else {
            return sb;
        }
    }

    private static String getBasicType(ServiceParameter parameter) {
        return "String";
    }

    private static String getCamelCode(String code) {
        return code.replaceFirst(code.substring(0, 1), code.substring(0, 1).toLowerCase());
    }

    private static String getMethodAnnotation(ServiceOperation operation) {
        StringBuilder sb = new StringBuilder();
        sb.append(getHttpMethod(operation));
        sb.append(getMethodTemplate(operation));
        ServiceParameter retParam = getReturnParam(operation);
        if (Objects.nonNull(retParam)) {
            if (retParam.isBasicType() && !retParam.isCollection() && retParam.getListDeepth() <= 0 && !EAPI_PARAM_TYPE_OBJECT.equals(retParam.getModelId())) {
                sb.append("  @Produces(MediaType.TEXT_PLAIN)").append(System.lineSeparator());
            }
        }
        return sb.toString();
    }

    private static ServiceParameter getReturnParam(ServiceOperation operation) {
        List<ServiceParameter> parameters = operation.getParameters();
        if (Objects.isNull(parameters) || parameters.size() <= 0) {
            return null;
        } else {
            List<ServiceParameter> retParams = parameters.stream().filter(ServiceParameter::isRetVal).collect(Collectors.toList());
            if (retParams.size() <= 0) {
                return null;
            } else {
                return retParams.get(0);
            }
        }
    }

    private static String getMethodTemplate(ServiceOperation operation) {
        StringBuilder sb = new StringBuilder();
        if (StringUtils.isNotBlank(operation.getTemplate())) {
            String template = operation.getTemplate();
            // 处理旧版元数据操作Template
            if (isContainChinese(template)) {
                sb.append("  @Path(\"/\")");
            } else {
                if (template.startsWith("/")) {
                    sb.append(MessageFormat.format("  @Path(\"{0}\")", template));
                } else {
                    sb.append((MessageFormat.format("  @Path(\"/{0}\")", template)));
                }
            }
        } else {
            sb.append("  @Path(\"/\")");
        }
        return sb.append(System.lineSeparator()).toString();
    }

    private static boolean isContainChinese(String str) {
        Matcher m = CHARACTER_PATTERN.matcher(str);
        return m.find();
    }

    private static String getHttpMethod(ServiceOperation operation) {
        StringBuilder sb = new StringBuilder();
        switch (operation.getHttpMethod()) {
            case "GET":
                sb.append("  @GET");
                break;
            case "POST":
                sb.append("  @POST");
                break;
            case "PUT":
                sb.append("  @PUT");
                break;
            case "PATCH":
                sb.append("  @PATCH");
                break;
            case "DELETE":
                sb.append("  @DELETE");
                break;
            default:
                break;
        }
        return sb.append(System.lineSeparator()).toString();
    }
}
